/* Copyright (c) 2015-2016 Ivan Grokhotkov. All rights reserved.
 * This file is part of eboot bootloader.
 *
 * Redistribution and use is permitted according to the conditions of the
 * 3-clause BSD license to be found in the LICENSE file.
 */
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "flash.h"
#include "eboot_command.h"

#define SWRST do { (*((volatile uint32_t*) 0x60000700)) |= 0x80000000; } while(0);

extern void ets_wdt_enable(void);
extern void ets_wdt_disable(void);

int print_version(const uint32_t flash_addr)
{
    uint32_t ver;
    if (SPIRead(flash_addr + APP_START_OFFSET + sizeof(image_header_t) + sizeof(section_header_t), &ver, sizeof(ver))) {
        return 1;
    }
    const char* __attribute__ ((aligned (4))) fmtt = "v%08x\n\0\0";
    uint32_t fmt[2];
    fmt[0] = ((uint32_t*) fmtt)[0];
    fmt[1] = ((uint32_t*) fmtt)[1];
    ets_printf((const char*) fmt, ver);
    return 0;
}

int load_app_from_flash_raw(const uint32_t flash_addr)
{
    image_header_t image_header;
    uint32_t pos = flash_addr + APP_START_OFFSET;
	
    if (SPIRead(pos, &image_header, sizeof(image_header))) {
        return 1;
    }
    pos += sizeof(image_header);


    for (uint32_t section_index = 0;
        section_index < image_header.num_segments;
        ++section_index)
    {
        section_header_t section_header = {0};
        if (SPIRead(pos, &section_header, sizeof(section_header))) {
            return 2;
        }
        pos += sizeof(section_header);

        const uint32_t address = section_header.address;

        bool load = false;

        if (address < 0x40000000) {
            load = true;
        }

        if (address >= 0x40100000 && address < 0x40108000) {
            load = true;
        }

        if (address >= 0x60000000) {
            load = true;
        }

        if (!load) {
            pos += section_header.size;
            continue;
        }

        if (SPIRead(pos, (void*)address, section_header.size))
            return 3;

        pos += section_header.size;
    }

    register uint32_t sp asm("a1") = 0x3ffffff0;
    register uint32_t pc asm("a3") = image_header.entry;
    __asm__  __volatile__ ("jx a3");

    return 0;
}



int copy_raw(const uint32_t src_addr,//source 
             const uint32_t dst_addr,//destination
             const uint32_t size)
{
    // require regions to be aligned
    if (src_addr & 0xfff != 0 ||
        dst_addr & 0xfff != 0) {
        return 1;
    }

    const uint32_t buffer_size = FLASH_SECTOR_SIZE;
    uint8_t buffer[buffer_size];
    uint32_t left = ((size+buffer_size-1) & ~(buffer_size-1));
    uint32_t saddr = src_addr;
    uint32_t daddr = dst_addr;
	
	uint8_t g=0;

	while (left) {
		ets_putc('g');//61
		
        if (SPIEraseSector(daddr/buffer_size)) {
            return 2;
        }
        if (SPIRead(saddr, buffer, buffer_size)) {
            return 3;
        }
#if 0		
		g++;
		ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('\r');ets_putc('\n');
		if((g==1)||(g==61))
		{
			for(int i=0;i<buffer_size;i++){
				uint8_t x=(buffer[i]>>4)&0xf;
				ets_putc(x>9?('A'+(x-10)):('0'+x));
				uint8_t y = buffer[i]&0xf;
				ets_putc(y>9?('A'+(y-10)):('0'+y));ets_putc(' ');
			}
		}
		ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('-');ets_putc('\r');ets_putc('\n');
#endif
		if (SPIWrite(daddr, buffer, buffer_size)) {
            return 4;
        }
        saddr += buffer_size;
        daddr += buffer_size;
        left  -= buffer_size;
    }

    return 0;
}

void pu32(uint32_t n){
	for(int i=0;i<8;i++){	
		uint8_t x=(n>>((7-i)*4))&0xf;	
		ets_putc(x>9?('A'+(x-10)):('0'+x));
	}
}

void reset_turnoff_pwm(int pin){
uint8_t DF_gpioToFn[16] = {0x34, 0x18, 0x38, 0x14, 0x3C, 0x40, 0x1C, 0x20, 0x24, 0x28, 0x2C, 0x30, 0x04, 0x08, 0x0C, 0x10};
#define DF_REG(addr) *((volatile uint32_t *)(0x60000000+(addr)))
#define GPF(p) DF_REG(0x800 + DF_gpioToFn[(p & 0xF)])
#define GPFFS(f) (((((f) & 4) != 0) << 8) | ((((f) & 2) != 0) << 5) | ((((f) & 1) != 0) << 4))
#define GPFFS_GPIO(p) (((p)==0||(p)==2||(p)==4||(p)==5)?0:((p)==16)?1:3)
#define GPC(p) DF_REG(0x328 + ((p & 0xF) * 4))
#define GPEC   DF_REG(0x314) //GPIO_ENABLE_CLR WO
#define GPES   DF_REG(0x310) 

	GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
	GPEC = (1 << pin); //Disable
	GPC(pin) = (GPC(pin) & (0xF << 7)) | (1 << 2);
	GPES = (1 << pin);
}

void main()
{
    int res = 9;
    struct eboot_command cmd;

	reset_turnoff_pwm(14);
	reset_turnoff_pwm(4);
	reset_turnoff_pwm(12);


    print_version(0);
#if 0
	ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('\n');
	pu32(cmd.magic);ets_putc('\n');
	pu32(cmd.action);ets_putc('\n');
	pu32(cmd.args[0]);ets_putc('\n');
	pu32(cmd.args[1]);ets_putc('\n');
	pu32(cmd.args[2]);ets_putc('\n');
	pu32(cmd.args[3]);ets_putc('\n');
	pu32(cmd.args[4]);ets_putc('\n');
	pu32(cmd.args[5]);ets_putc('\n');
	pu32(cmd.args[6]);ets_putc('\n');
	pu32(cmd.args[7]);ets_putc('\n');
	pu32(cmd.args[8]);ets_putc('\n');
	pu32(cmd.args[9]);ets_putc('\n');
	pu32(cmd.crc32);ets_putc('\n');
	ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('\n');
#endif


    if (eboot_command_read(&cmd) == 0) {
        // valid command was passed via RTC_MEM
        eboot_command_clear();
        ets_putc('@');
    } else {
        // no valid command found
        cmd.action = ACTION_LOAD_APP;
        cmd.args[0] = 0;
        ets_putc('~');
    }

#if 0
	ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('\n');
	pu32(cmd.magic);ets_putc('\n');
	pu32(cmd.action);ets_putc('\n');
	pu32(cmd.args[0]);ets_putc('\n');
	pu32(cmd.args[1]);ets_putc('\n');
	pu32(cmd.args[2]);ets_putc('\n');
	pu32(cmd.args[3]);ets_putc('\n');
	pu32(cmd.args[4]);ets_putc('\n');
	pu32(cmd.args[5]);ets_putc('\n');
	pu32(cmd.args[6]);ets_putc('\n');
	pu32(cmd.args[7]);ets_putc('\n');
	pu32(cmd.args[8]);ets_putc('\n');
	pu32(cmd.args[9]);ets_putc('\n');
	pu32(cmd.crc32);ets_putc('\n');
	ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('*');ets_putc('\n');
#endif


    if (cmd.action == ACTION_COPY_RAW) {
        ets_putc('c'); ets_putc('p'); ets_putc(':');ets_putc('\r');ets_putc('\n');
        ets_wdt_disable();
        res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2]);
#if 0
		pu32(cmd.args[0]);ets_putc('\r');ets_putc('\n');
		pu32(cmd.args[1]);ets_putc('\r');ets_putc('\n');
		pu32(cmd.args[2]);ets_putc('\r');ets_putc('\n');
#endif
        ets_wdt_enable();
		
        ets_putc('0'+res);ets_putc('\r');ets_putc('\n');
        if (res == 0) {
            cmd.action = ACTION_LOAD_APP;
            cmd.args[0] = cmd.args[1];
        }
    }



    if (cmd.action == ACTION_LOAD_APP) {
        ets_putc('l'); ets_putc('d'); ets_putc('\n');

        res = load_app_from_flash_raw(cmd.args[0]);

        //we will get to this only on load fail
        ets_putc('e'); ets_putc(':'); ets_putc('0'+res); ets_putc('\n');
    }

	
    if (res) {
        SWRST;
    }

    while(true){}
}
